/**
 * 文字显示 - NPM包 troika-three-text 的封装
 *
 * 用法：
 *   const text = new Text(width, height, options)
 *   scene.add(text)
 */

import * as THREE from 'three'
// @ts-ignore
import { Text as TroikaThreeText } from 'troika-three-text'

export type TextOptions = {
  width?: number
  height?: number
  position?: [number, number, number]
  fontSize?: number
  fontColor?: string | number
  fontWeight?: string
  opacity?: number
  outlineWidth?: number
  outlineColor?: string | number
  lineHeight?: number
  letterSpacing?: number
  textIndent?: number
  showHelper?: boolean
  fontUrl?: string
  clip?: boolean
  textAlign?: TextAlign
  verticalAlign?: VerticalAlign
  padding?: number
}
type TextAlign = 'left' | 'center' | 'right'
type VerticalAlign = 'top' | 'center' | 'bottom'

export default class Text extends THREE.Object3D {
  // 静态
  static DEFAULT_FONT_SIZE = 0.2
  static DEFAULT_FONT_COLOR = 0xffffff
  static DEFAULT_FONT_WEIGHT = 'normal'
  // 主属性
  text0: any // 被封装的原文字对象
  helper = new THREE.Group() // 辅助器
  // 主设置
  text: string // 文字内容
  width = 1 // 文字区域宽度
  height = 0.5 // 文字区域高度
  fontSize = Text.DEFAULT_FONT_SIZE // 字号
  fontColor: string | number = Text.DEFAULT_FONT_COLOR // 字色
  fontWeight = Text.DEFAULT_FONT_WEIGHT // 字重
  fontUrl?: string // 字体文件路径，只支持字体格式 TTF, OTF, WOFF
  opacity = 1 // 不透明度
  // 描边
  outlineWidth = 0 // 描边粗细，默认0，不描边
  outlineColor: string | number = 0x000000 // 描边色，默认黑色
  // 布局设置
  lineHeight = 1.4 // 行高，单位为几倍文字大小，默认取前端国际标准:1.4
  letterSpacing = 0.04 // 字符间距，单位为几倍文字大小
  textIndent = 0 // 段落缩进
  clip = true // 裁剪超出矩形区域的
  padding = 0.05 // 内边距
  textAlign: TextAlign = 'left' // 水平对齐
  verticalAlign: VerticalAlign = 'top' // 垂直对齐
  constructor(text: string, options: TextOptions = {}) {
    super()

    // 主属性
    this.text = text
    if (options.width != null) this.width = options.width
    if (options.height != null) this.height = options.height
    if (options.position) this.position.set(...options.position)
    if (options.fontSize != null) this.fontSize = options.fontSize
    if (options.fontColor != null) this.fontColor = options.fontColor
    if (options.fontWeight != null) this.fontWeight = options.fontWeight
    if (options.fontUrl) {
      const url = options.fontUrl.trim().toLowerCase()
      const isTtf = url.endsWith('.ttf')
      const isOtf = url.endsWith('.otf')
      const isWoff = url.endsWith('.woff')
      if (isTtf || isOtf || isWoff) this.fontUrl = options.fontUrl
      else console.warn('[Text] 字体文件只支持 TTF, OTF, WOFF 格式')
    }
    if (options.opacity != null) this.opacity = options.opacity
    // 描边
    if (options.outlineWidth != null) this.outlineWidth = options.outlineWidth
    if (options.outlineColor != null) this.outlineColor = options.outlineColor
    // 布局
    if (options.lineHeight != null) this.lineHeight = options.lineHeight
    if (options.letterSpacing != null) this.letterSpacing = options.letterSpacing
    if (options.textIndent != null) this.textIndent = options.textIndent
    if (options.clip != null) this.clip = options.clip
    if (options.textAlign) this.textAlign = options.textAlign
    if (options.verticalAlign) this.verticalAlign = options.verticalAlign

    // 创建对象
    this.createText()
    this.add(this.helper)
    if (!options.showHelper) this.helper.visible = false
    this.createHelper()
    this.updateText()
  }

  createText() {
    const text = new TroikaThreeText()
    text.text = this.text
    text.color = this.fontColor
    text.fontWeight = this.fontWeight
    text.material.opacity = this.opacity
    text.depthOffset = -1
    text.fontSize = this.fontSize
    if (this.fontUrl) text.font = this.fontUrl
    // 描边
    text.outlineWidth = this.outlineWidth
    text.outlineColor = this.outlineColor
    // 布局
    text.lineHeight = this.lineHeight
    text.letterSpacing = this.letterSpacing
    text.textIndent = this.textIndent * this.fontSize * (1 + this.letterSpacing)
    text.maxWidth = this.width - this.padding * 2
    text.overflowWrap = 'break-word' // 英文按说不该这样，但不这样的话，中文段落就不会换行
    // 水平对齐
    let [clipMinX, clipMinY] = [0, 0]
    if (this.textAlign == 'left') {
      text.anchorX = 'left'
      text.position.x = -this.width / 2 + this.padding
      clipMinX = 0
    } else if (this.textAlign == 'center') {
      text.anchorX = 'center'
      text.position.x = 0
      clipMinX = -this.width / 2 + this.padding
      text.textAlign = 'center'
    } else {
      text.anchorX = 'right'
      text.position.x = this.width / 2 - this.padding
      clipMinX = -this.width + this.padding * 2
      text.textAlign = 'right'
    }
    // 垂直对齐
    if (this.verticalAlign == 'top') {
      text.anchorY = 'top'
      text.position.y = this.height / 2 - this.padding
      clipMinY = -this.height + this.padding * 2
    } else if (this.verticalAlign == 'center') {
      text.anchorY = 'middle'
      text.position.y = 0
      clipMinY = -this.height / 2 + this.padding
    } else {
      text.anchorY = 'bottom'
      text.position.y = -this.height / 2 + this.padding
      clipMinY = 0
    }
    const clipMaxX = clipMinX + this.width - this.padding * 2
    const clipMaxY = clipMinY + this.height - this.padding * 2
    // console.log('clip', clipMinX, clipMinY, clipMaxX, clipMaxY)
    if (this.clip) text.clipRect = [clipMinX, clipMinY, clipMaxX, clipMaxY]
    this.add(text)
    this.text0 = text
  }

  createHelper() {
    const rectOuter = new THREE.LineSegments(
      new THREE.EdgesGeometry(new THREE.PlaneGeometry(this.width, this.height)),
      new THREE.LineBasicMaterial({ color: 0x00ffff, transparent: true, opacity: 0.5 }),
    )
    this.helper.add(rectOuter)
    const rectInner = new THREE.LineSegments(
      new THREE.EdgesGeometry(
        new THREE.PlaneGeometry(this.width - this.padding * 2, this.height - this.padding * 2),
      ),
      new THREE.LineBasicMaterial({ color: 0xffff00, transparent: true, opacity: 0.5 }),
    )
    this.helper.add(rectInner)
  }

  setText(text: string) {
    this.text0.text = text
    this.updateText()
  }

  setColor(color: number) {
    this.text0.color = color
    this.updateText()
  }

  setFontSize(size: number) {
    this.text0.fontSize = size
    this.updateText()
  }

  setFontWeight(weight: string) {
    this.text0.fontWeight = weight
    this.updateText()
  }

  updateText() {
    this.text0.sync()
  }

  showHelper() {
    this.helper.visible = true
  }

  hideHelper() {
    this.helper.visible = false
  }
}
